#include "Map.h"

//set invalid values
Map::Map() : m_width(-1), m_height(-1), m_data(NULL), m_x(0), m_y(0)
{}
//constructor from file
Map::Map(char * a_filename)
{
	Map();//forces values to invalid
	load(a_filename);//get good values
}
//deconstructor
Map::~Map()
{
	release();
}

bool Map::load(char * a_filename)
{
	ifstream in(a_filename);
	if(in.is_open())
	{
		load(in);
		in.close();
		return true;
	}
	return false;
}
void Map::load(ifstream &a_source)
{
	//clear map first
	release();


	a_source >> m_width;
	a_source >> m_height;
	//ignore the rest of this line
	//a_source.ignore(INT_MAX, '\n');
	a_source.ignore(1024, '\n');
	m_data = new char * [m_height];
	//for each line of the map
	for(int r = 0; r < m_height; ++r)
	{
		//allocate a buffer to hold the line
		m_data[r] = new char[m_width];
		//fill the line with data from the file
		for(int c = 0; c < m_width; ++c)
		{
			m_data[r][c] = a_source.get();
		}
		//ignore the rest of the data in the line after m_width characters
		a_source.ignore(1024, '\n');
	}

}

void Map::draw()
{
	draw(m_x, m_y);
}

void Map::draw(int a_x, int a_y)
{
	for(int r = 0; r < m_height; ++r)
	{
		for(int c = 0; c < m_width; ++c)
		{
			if(m_data[r][c] != ' ')
			{
				gotoxy(a_x + c, a_y + r);
				cout << m_data[r][c];
			}
		}
	}
}

int Map::getHeight()
{
	return m_height;
}

int Map::getWidth()
{
	return m_width;
}
int Map::getX()
{
	return m_x;
}
int Map::getY()
{
	return m_y;
}
void Map::setX(int a_x)
{
	m_x = a_x;
}
void Map::setY(int a_y)
{
	m_y = a_y;
}
void Map::setXY(int a_x, int a_y)
{
	setX(a_x);
	setY(a_y);
}
void Map::addX(int a_x)
{
	m_x += a_x;
}
void Map::addY(int a_y)
{
	m_y += a_y;
}
void Map::release()
{
	//if there is currently a map, we need to clean it up before the
	//next one loads
	if(m_data != NULL)
	{
		for(int r = 0; r < m_height; ++r)
		{
			delete m_data[r];
		}
		delete m_data;
		m_data = NULL;
	}
	m_width = m_height = -1;//0;
}


//draws a map horizontally flipped
void Map::drawFlippedH(int a_x, int a_y)
{
	for(int r = 0; r < m_height; ++r)
	{
		gotoxy(a_x, a_y + r);
		for(int c = 0; c < m_width; ++c)
		{
			cout << m_data[r][(m_width-1)-c];
		}
		cout << endl;
	}
}

//draws a map vertically flipped
void Map::drawFlippedV(int a_x, int a_y)
{
	for(int r = 0; r < m_height; ++r)
	{
		gotoxy(a_x, a_y + r);
		for(int c = 0; c < m_width; ++c)
		{
			cout << m_data[(m_height-1)-r][c];
		}
		cout << endl;
	}
}

//draws a map vertically AND horizontally flipped
void Map::drawFlippedXY(int a_x, int a_y)
{
	for(int r = 0; r < m_width; ++r)		// notice r < WIDTH
	{
		gotoxy(a_x, a_y + r);
		for(int c = 0; c < m_height; ++c)	// notice c < HEIGHT
		{
			cout << m_data[c][r];
		}
		cout << endl;
	}
}

//draws a map rotated clockwise
void Map::drawRotatedCW(int a_x, int a_y)
{
	for(int r = 0; r < m_width; ++r)		// notice r < WIDTH
	{
		gotoxy(a_x, a_y + r);
		for(int c = 0; c < m_height; ++c)	// notice c < HEIGHT
		{
			cout << m_data[(m_height-1)-c][r];
		}
		cout << endl;
	}
}

//draws a map rotated counter-clockwise
void Map::drawRotatedCCW(int a_x, int a_y)
{
	for(int r = 0; r < m_width; ++r)		// notice r < WIDTH
	{
		gotoxy(a_x, a_y + r);
		for(int c = 0; c < m_height; ++c)	// notice c < HEIGHT
		{
			cout << m_data[c][(m_width-1)-r];
		}
		cout << endl;
	}
}

//flips the map horizontally
//modifies the map permanently
void Map::flipH()
{
	char ** temp = NULL;
	temp = new char * [m_height];
	int m = 0;

	//first flip the rows by accessing the columns in reverse
	//then save the values to a temp array which is accessed normally
	for(int r = 0; r < m_height; ++r)
	{
		temp[r] = new char[m_width];
		for(int c = m_width - 1; c >= 0; --c)
		{
			temp[r][m] = m_data[r][c];
			m++;
		}
		m = 0;

	}

	//go back through and save all the flipped data from the temp array
	//back into m_data
	//then delete temp
	for(int r = 0; r < m_height; ++r)
		{
			for(int c = 0; c < m_width; ++c)
			{
				m_data[r][c] = temp[r][c];
			}
			delete temp[r];
		}
	delete temp;
}

//flips the map vertically
//modifies the map permanently
void Map::flipV()
{
	char ** temp = NULL;
	temp = new char * [m_height];
	int n = 0;

	//first flip the columns by accessing the rows in reverse
	//then save the values to a temp array which is accessed normally
	for(int r = m_height - 1; r >= 0; --r)
	{
		temp[n] = new char[m_width];
		for(int c = 0; c < m_width; ++c)
		{
			temp[n][c] = m_data[r][c];

		}
		n++;

	}
	//go back through and save all the flipped data from the temp array
	//back into m_data
	//then delete temp
	for(int r = 0; r < m_height; ++r)
		{
			for(int c = 0; c < m_width; ++c)
			{
				m_data[r][c] = temp[r][c];
			}
			delete temp[r];
		}
	delete temp;
}

//flips the map vertically AND horizontally
//modifies the map permanently
void Map::flipXY()
{
	char ** temp = NULL;
	temp = new char * [m_height];
	int n = 0, m = 0;

	//accessing the rows and columns in reverse
	//then save the values to a temp array which is accessed normally
	for(int r = m_height - 1; r >= 0; --r)
	{
		temp[n] = new char[m_width];
		for(int c = m_width - 1; c >= 0; --c)
		{
			temp[n][m] = m_data[r][c];
			m++;

		}
		m = 0;
		n++;

	}
	//go back through and save all the flipped data from the temp array
	//back into m_data
	//then delete temp
	for(int r = 0; r < m_height; ++r)
		{
			for(int c = 0; c < m_width; ++c)
			{
				m_data[r][c] = temp[r][c];
			}
			delete temp[r];
		}
	delete temp;
}

//rotates the map clockwise
//modifies the map permanently
void Map::rotateCW()
{
	char ** temp = NULL;
	temp = new char * [m_width];
	//int n = 0, m = 0;
	int m = 0;
	int sizeTemp = 0;
	int tempWidth, tempHeight;

	//accessing columns first, and then access the rows in reverse
	//then save the values to a temp array which is accessed normally
	//delete after saving it to temp
	for(int c = 0; c < m_width; ++c)
	{
		temp[c] = new char[m_height];
		for(int r = m_height - 1; r >= 0; --r)
		{
			temp[c][m] = m_data[r][c];
			m++;

		}
		m = 0;

	}

	//save m_width and m_height
	tempWidth = m_width;
	tempHeight = m_height;
	//delete m_data
	release();
	//reassign m_width and m_height
	m_width = tempHeight;
	m_height = tempWidth;

	//go back through and save all the flipped data from the temp array
	//back into recreated m_data
	//then delete temp
	m_data = new char * [m_height];
	for(int r = 0; r < m_height; ++r)
		{
			m_data[r] = new char[m_width];
			for(int c = 0; c < m_width; ++c)
			{
				m_data[r][c] = temp[r][c];
			}
			delete temp[r];
		}
	delete temp;
}

//rotates the map counter-clockwise
//modifies the map permanently
void Map::rotateCCW()
{
	char ** temp = NULL;
	temp = new char * [m_width];
	//int n = 0, m = 0;
	int m = 0;
	int sizeTemp = 0;
	int tempWidth, tempHeight;

	//accessing columns in reverse first, and then access the rows
	//then save the values to a temp array which is accessed normally
	//delete after saving it to temp
	for(int c = m_width - 1; c >= 0; --c)
	{
		temp[m] = new char[m_height];
		for(int r = 0; r < m_height; ++r)
		{
			temp[m][r] = m_data[r][c];
		}
		m++;
	}

	//save m_width and m_height
	tempWidth = m_width;
	tempHeight = m_height;
	//delete m_data
	release();
	//reassign m_width and m_height
	m_width = tempHeight;
	m_height = tempWidth;

	//go back through and save all the flipped data from the temp array
	//back into recreated m_data
	//then delete temp
	m_data = new char * [m_height];
	for(int r = 0; r < m_height; ++r)
		{
			m_data[r] = new char[m_width];
			for(int c = 0; c < m_width; ++c)
			{
				m_data[r][c] = temp[r][c];
			}
			delete temp[r];
		}
	delete temp;
}

//checks to see if the current maps value is overlapping values
//of another map
bool Map::overlaps(char a_myValue, char a_hisValue, Map & a_otherMap)
{
	int xOffset = m_x - a_otherMap.m_x;
	int yOffset = m_y - a_otherMap.m_y;

	for(int r = 0; r < m_height; ++r)
	{
		for(int c = 0; c < m_width; ++c)
		{
			if(c+xOffset >= 0
				&& r+yOffset >= 0
				&& c+xOffset < a_otherMap.m_width
				&& r+yOffset < a_otherMap.m_height
				&& m_data[r][c] == a_myValue
				&& a_otherMap.m_data[r+yOffset][c+xOffset] == a_hisValue)
			{
				return true;
			}
		}
	}
	return false;
}

//merges this maps values with the values of a base map
void Map::mergeWithBaseMap(char a_myValue, char a_hisValue, Map & a_baseMap)
{
	for(int r = 0; r < m_height; ++r)
	{
		for(int c = 0; c < m_width; ++c)
		{
			//fill in places that had characters with the base maps character
			if(m_data[r][c] == a_myValue)
			{
				a_baseMap.m_data[r+m_y][c+m_x] = a_hisValue;
			}
		}
	}
	//delete this map after merging
	release();
}
//takes input for this map
//changes m_x and m_y
//performs collision detection (asking for forgiveness)
//and determines if the map needs to be merged
bool Map::handleInput(Map & a_otherMap, char a_input)
{
	int oldX = m_x;
	int oldY = m_y;

	int howWeRotated = NO_ROTATION;
	switch(a_input)
	{
	//case 'w':	m_y--; break; //up		DISABLED
	case 'a':	m_x--; break;//left
	case 's':	m_y++; break;//down
	case 'd':	m_x++; break;//right
	case 'q':	
		rotateCW(); 
		howWeRotated = CW_ROTATION;
		break;
	case 'e':	
		rotateCCW(); 
		howWeRotated = CCW_ROTATION;
		break;
	}

	if(collisionDetection(a_otherMap, oldX, oldY, howWeRotated))
		return true;//tell game to set a new piece
	else
		return false;
}

//checks for collison with boundaries and values on another map
//checks to see if the current map needs to be merged with the base
//if there is any collision, oldX and oldY values are reset
bool Map::collisionDetection(Map & a_otherMap, int oldX, int oldY, int howWeRotated)
{
	bool needToMerge = false;
	//COLLISION DETECTION
	if(m_x < a_otherMap.m_x //too far left
		|| m_y < a_otherMap.m_y //too far up
		|| m_x+m_width > 
			a_otherMap.m_x+a_otherMap.getWidth()//too far right
		|| m_y+m_height > 
			a_otherMap.m_y+a_otherMap.getHeight()//too far down
			|| overlaps('*', '#', a_otherMap))//hit another piece/obstacle
	{
		//If piece collides with bottom or another screen
		//set bool flag, then undo movement and merge
		if(m_y+m_height >
				a_otherMap.m_y+a_otherMap.getHeight() //reaches the bottom
				|| overlaps('*', '#', a_otherMap))//collides with another piece
		{
			needToMerge = true;
		}

		//Undo movement
		m_x = oldX;
		m_y = oldY;
		switch(howWeRotated)
		{
		case CW_ROTATION:
			rotateCCW();
		case CCW_ROTATION:
			rotateCW();
		}

		//merge
		if(needToMerge)
		{
			mergeWithBaseMap('*', '#', a_otherMap);
			return true; //tell game to set a new piece
		}
	}
	return false;
}

//checks to see if the map is null
bool Map::isNull()
{
	if(m_data == NULL)
	{
		return true;
	}
	else
	{
		return false;
	}
}
//overload the = operator
void Map::operator=(Map & a_otherMap)
{
	//release current map data
	release();

	//get new dimentsions
	m_width = a_otherMap.m_width;
	m_height = a_otherMap.m_height;

	//create new 2d array for m_data
	m_data = new char * [m_height];

	for(int r = 0; r < m_height; ++r)
	{
		m_data[r] = new char[m_width];
		for(int c = 0; c < m_width; ++c)
		{
			//copy data
			m_data[r][c] = a_otherMap.m_data[r][c];
		}
	}
}
//get the value at a single element in the map
char Map::getValue(int row, int col)
{
	return m_data[row][col];
}
//erase an entire row and move everything down
void Map::eraseRow(int row)
{
	//access rows in reverse starting from the row that
	//needs to be erased
	for(int r = row; r >= 0; r--)
	{
		for(int c = 0; c < m_width; c++)
		{
			// and erase the spot where it used to be
			if((r-1) >= 0)
				m_data[r][c] = m_data[r-1][c];
			else
				m_data[r][c] = '.';
		}
	}
}